//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this sample source code is subject to the terms of the Microsoft
// license agreement under which you licensed this sample source code. If
// you did not accept the terms of the license agreement, you are not
// authorized to use this sample source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the LICENSE.RTF on your install media or the root of your tools installation.
// THE SAMPLE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES OR INDEMNITIES.
//
/*++
THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.
Copyright (c) 1995-2003  Microsoft Corporation

Module Name: chanel.cpp

Abstract:


Notes:


--*/
//
//
// (C) Copyright 2007 Marvell International Ltd.
//  All Rights Reserved
//

#include "chanel.h"
#include <pegdser.h>

//=============================================================
// WinCE exception management
//=============================================================
DWORD ChannelFilterException(DWORD dwExceptionCode, wchar_t *pMsg)
{
    // send log message
    SENDERR((TEXT("Mux 07.10 - Channel EXCEPTION : 0x%x in %s\r\n"), dwExceptionCode, pMsg)) ;

    return EXCEPTION_CONTINUE_SEARCH ;
}
//=============================================================

// static data
//--------------
CDispatcher CChanel::s_Dispatcher ;

unsigned char	CChanel::s_ucCrcTbl[256] = 
{   
    //reversed, 8-bit, poly=0x07
    0x00, 0x91, 0xE3, 0x72, 0x07, 0x96, 0xE4, 0x75,
    0x0E, 0x9F, 0xED, 0x7C, 0x09, 0x98, 0xEA, 0x7B,

    0x1C, 0x8D, 0xFF, 0x6E, 0x1B, 0x8A, 0xF8, 0x69,
    0x12, 0x83, 0xF1, 0x60, 0x15, 0x84, 0xF6, 0x67,

    0x38, 0xA9, 0xDB, 0x4A, 0x3F, 0xAE, 0xDC, 0x4D,
    0x36, 0xA7, 0xD5, 0x44, 0x31, 0xA0, 0xD2, 0x43,

    0x24, 0xB5, 0xC7, 0x56, 0x23, 0xB2, 0xC0, 0x51,
    0x2A, 0xBB, 0xC9, 0x58, 0x2D, 0xBC, 0xCE, 0x5F,

    0x70, 0xE1, 0x93, 0x02, 0x77, 0xE6, 0x94, 0x05,
    0x7E, 0xEF, 0x9D, 0x0C, 0x79, 0xE8, 0x9A, 0x0B,

    0x6C, 0xFD, 0x8F, 0x1E, 0x6B, 0xFA, 0x88, 0x19,
    0x62, 0xF3, 0x81, 0x10, 0x65, 0xF4, 0x86, 0x17,

    0x48, 0xD9, 0xAB, 0x3A, 0x4F, 0xDE, 0xAC, 0x3D,
    0x46, 0xD7, 0xA5, 0x34, 0x41, 0xD0, 0xA2, 0x33,

    0x54, 0xC5, 0xB7, 0x26, 0x53, 0xC2, 0xB0, 0x21,
    0x5A, 0xCB, 0xB9, 0x28, 0x5D, 0xCC, 0xBE, 0x2F,

    0xE0, 0x71, 0x03, 0x92, 0xE7, 0x76, 0x04, 0x95,
    0xEE, 0x7F, 0x0D, 0x9C, 0xE9, 0x78, 0x0A, 0x9B,

    0xFC, 0x6D, 0x1F, 0x8E, 0xFB, 0x6A, 0x18, 0x89,
    0xF2, 0x63, 0x11, 0x80, 0xF5, 0x64, 0x16, 0x87,

    0xD8, 0x49, 0x3B, 0xAA, 0xDF, 0x4E, 0x3C, 0xAD,
    0xD6, 0x47, 0x35, 0xA4, 0xD1, 0x40, 0x32, 0xA3,

    0xC4, 0x55, 0x27, 0xB6, 0xC3, 0x52, 0x20, 0xB1,
    0xCA, 0x5B, 0x29, 0xB8, 0xCD, 0x5C, 0x2E, 0xBF,

    0x90, 0x01, 0x73, 0xE2, 0x97, 0x06, 0x74, 0xE5,
    0x9E, 0x0F, 0x7D, 0xEC, 0x99, 0x08, 0x7A, 0xEB,

    0x8C, 0x1D, 0x6F, 0xFE, 0x8B, 0x1A, 0x68, 0xF9,
    0x82, 0x13, 0x61, 0xF0, 0x85, 0x14, 0x66, 0xF7,

    0xA8, 0x39, 0x4B, 0xDA, 0xAF, 0x3E, 0x4C, 0xDD,
    0xA6, 0x37, 0x45, 0xD4, 0xA1, 0x30, 0x42, 0xD3,

    0xB4, 0x25, 0x57, 0xC6, 0xB3, 0x22, 0x50, 0xC1,
    0xBA, 0x2B, 0x59, 0xC8, 0xBD, 0x2C, 0x5E, 0xCF
};

HANDLE CChanel::s_hFCONEv = NULL ;

// this name is used when param is missing in user channel const
wchar_t g_szNullName[] = TEXT("?") ;

//-------------------
// CChanle methods 
//-------------------

// contructor / destructor
//--------------------------

CChanel::CChanel(BYTE bDlci, LPTHREAD_START_ROUTINE ProtocolTh, bool (*pDerivedClassTerminate) (PVOID)) 
{
    // Dispatcher reset
    //------------------
    if (!s_Dispatcher)
    {
        s_Dispatcher.ResetInternalData() ;
    }

    // Init
    //------------------------
    
    m_bAddress = (bDlci << 2) | SET_CR_MASK | SET_EA_MASK;

    // channel is disconnected
    m_lState = DISCONNECTED ;
    m_bIsOk = false ;
    m_bTerminate = true ;

    // init internal variables
    m_hEndOfFrameEv = NULL ;
    m_hRespEv = NULL ;
    m_hProtocolTh = NULL ;

    m_bFrameFlag = GSM0710_BASIC_FLAG;
    m_pfnSend = BasicModeSend;

    m_cbMaxFrameData = 0;
    m_pSend = NULL;
 
    m_bControl = 0;    

    m_cbRecBuf = 0;
    m_pDataBuf = NULL;
    m_pRec = m_pRead = m_pWrite = NULL;

    // init critical section
    InitializeCriticalSection(&m_csReadLocker) ;
    InitializeCriticalSection(&m_csSendLocker) ;
    InitializeCriticalSection(&m_csPollFrameLocker);

    // derived class related pointers
    m_ProtocolTh = ProtocolTh ;
    m_pDerivedClassTerminate = pDerivedClassTerminate ;
}

CChanel::~CChanel()
{
    if (m_bIsOk)
    {
        Terminate() ;
    }

    // delete critical section
    DeleteCriticalSection(&m_csReadLocker) ;
    DeleteCriticalSection(&m_csSendLocker) ;
    DeleteCriticalSection(&m_csPollFrameLocker);
}

bool CChanel::Init()
{
    tFrameParam FrameParam;
    DWORD cbSendBuf;

    SENDLOG((TEXT("710MUX: Chnl %s: -> Init\r\n"), m_pName)) ;

    if (m_bIsOk)
    {
        // channel already initialized
        return true ;
    }

    // check if the dynamical init can be performed
    //-----------------------------------------------
    if (!s_Dispatcher)
    {
        // dispatcher is not ready => do init
        if (!s_Dispatcher.Init())
        {
            goto error ;
        }
    }
    else if ( s_Dispatcher.IsDlciAlreadyBooked(this) )
    {
        // this Channel (DLCI) is already existing
        // => exit immediatly
        //
        // rem : do not jump to error
        //		 because it will also terminate  
        //		 the already existing channel
        SENDERR((TEXT("710MUX: Chnl %s: -> ERROR dlci already exist\r\n"), m_pName)) ;
        return false ;
    }

    // create threads and synchro mechanisms
    //----------------------------------------
    m_bTerminate = false ;

    // create events
    m_hEndOfFrameEv = CreateEvent(0,FALSE,FALSE,NULL) ;
    m_hRespEv = CreateEvent(0,FALSE,FALSE,NULL) ;
    if (NULL == m_hEndOfFrameEv || NULL == m_hRespEv)
    {
        goto error ;
    }

    // control flow
    if (NULL == s_hFCONEv)
    {
        // manual reset event already set
        //
        // rem:
        // since it is a static member of the class it should only
        // be initialized once
        //
        s_hFCONEv = CreateEvent(NULL, TRUE, TRUE, NULL) ;
        if (NULL == s_hFCONEv)
        {
            goto error ;
        }
    }

    // create threads
    //
    // Rem : 'this' ptr is actually 'this' ptr from the derived class
    //		 => address point to
    //			-> based class (1st)
    //			-> derived class specific (2nd)
    //		 therefore the Protocol Thread can cast its parameter to
    //		 the relevant derived class without any risk of having the 
    //		 wrong base address
    m_hProtocolTh = CreateThread(NULL,0, m_ProtocolTh, (LPVOID)this, 0,NULL) ;
    if (NULL == m_hProtocolTh)
    {
        goto error ;
    }

    // Register this chanel
    //-------------------------------------------   
    if ( !s_Dispatcher.Register(this, &FrameParam) )
    {
        goto error ;
    }
    
    //Allocate buffers
    m_cbMaxFrameData = FrameParam.dwMaxDataSize;
    cbSendBuf = 2 * (FrameParam.dwMaxDataSize + FRAME_SIZE_MIN) + 2;   
    m_cbRecBuf = REC_BUFFER_SIZE;

    m_pDataBuf = (BYTE *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cbSendBuf + m_cbRecBuf);

    if (NULL == m_pDataBuf)
    {
        goto error;
    }

    m_pSend = m_pDataBuf;
    m_pRec = (m_pDataBuf + cbSendBuf);
    m_pRead = m_pWrite = m_pRec;
    
    //Init send function
    m_bFrameFlag = FrameParam.bFlag;
    if (GSM0710_ADVANCED_FLAG == m_bFrameFlag)
    {
        m_pfnSend = AdvancedModeSend;
    }
    else
    {
        m_pfnSend = BasicModeSend;
    }

    m_bControl = 0;    

    m_bIsOk = true ;

    // set the chanel state
    //----------------------
    // signal the new chanel to TE
    if ( !SendSABM() )
    {
        goto error ;
    }

    // channel is now connected
    InterlockedExchange(&m_lState, CONNECTED) ;
    InterlockedIncrement(&s_Dispatcher.m_lConnectedChanelNb) ;

    SENDLOG((TEXT("710MUX: Chnl %s: -> Init OK\r\n"), m_pName)) ;

    return true ;

error:
    Terminate();
    return false ;
}

void CChanel::Terminate()
{
    BYTE ucDLCI = 0;
	BYTE ucUIHData[2];
	
	
    if (!m_bIsOk)
    {
        // channel already terminated
        return ;
    }

    SENDLOG((TEXT("710MUX: CChanel - Terminate %s\r\n"), m_pName)) ;

    // if connected => disconnect
    if (m_bIsOk && DISCONNECTED != m_lState )
    {
        // don't care about the answer, 
        // since this channel is terminating
        SendDISC() ;

        // channel is now disconnected
        InterlockedDecrement(&s_Dispatcher.m_lConnectedChanelNb) ;
        InterlockedExchange(&m_lState, DISCONNECTED) ;
    }

    ucDLCI = GetDLCI();

	if (0 == ucDLCI)
	{
	    /* CTRL Channel */
		/* Exit MUX. Closed Down */
        ucUIHData[0] = CTRL_UIH_CLD | CR_BIT | SET_EA_MASK ;
        ucUIHData[1] = SET_EA_MASK ;				
        if (sizeof(ucUIHData) != SendUIH(ucUIHData, sizeof(ucUIHData), m_bUIHControl))
    	{
		    SENDERR((TEXT("710MUX: CChanel - SendUIH(CLD) Error\r\n"))) ;		
    	}

		Sleep(500);
	}

    // channel is no longer usable
    //--------------------------------
    m_bIsOk = false ;
    m_bTerminate = true ;

    // terminate threads
    //-------------------
    if (NULL != m_hProtocolTh)
    {
        // wake up thread
        SetEvent(m_hEndOfFrameEv) ;

        // wait end of thread
        WaitForSingleObject(m_hProtocolTh, INFINITE) ;
        CloseHandle(m_hProtocolTh) ; 
        m_hProtocolTh	= NULL ;
    }

    // close event handles
    //----------------------
    if (NULL != m_hEndOfFrameEv)
    {
        CloseHandle(m_hEndOfFrameEv) ;
        m_hEndOfFrameEv = NULL ;
    }

    if (NULL != m_hRespEv)
    {
        // unblock the waiting threads
        //
        // Rem : waiting threads should check
        //	     m_bIsOk to determine the reason
        //		 of signaling this event:
        //		 - bIsOK = true		=> frame have been received
        //		 - m_bIsOk = false	=> terminating the channel
        SetEvent(m_hRespEv) ;

        CloseHandle(m_hRespEv) ;
        m_hRespEv = NULL ;
    }

    // unregister from dispatcher
    s_Dispatcher.Unregister(this) ;

    //Free allocated buffers
    if (m_pDataBuf != NULL)
    {
        EnterCriticalSection(&m_csReadLocker);
        m_cbRecBuf = 0;
        m_pRec = m_pRead = m_pWrite = NULL;
        LeaveCriticalSection(&m_csReadLocker);

        EnterCriticalSection(&m_csSendLocker);
        m_pSend = NULL;
        m_cbMaxFrameData = 0;
        LeaveCriticalSection(&m_csSendLocker);

        HeapFree(GetProcessHeap(), 0, m_pDataBuf);
    }  

    // call derived class specific Terminate method
    //-----------------------------------------------
    if ( NULL != m_pDerivedClassTerminate )
    {
        m_pDerivedClassTerminate((PVOID) this) ;
    }

    // terminate dispatcher
    //
    // rem: this one should not terminate
    //		if there is still other 
    //		user channel(s) opened
    //-------------------------------------
    s_Dispatcher.TryTerminate() ;
}

// logical operators
//---------------------
bool CChanel::operator!()
{
    return (!s_Dispatcher || !m_bIsOk) ;
}

BYTE CChanel::CalcFCS(BYTE* pBuf, DWORD cbBuf)
{
    BYTE bFCS = 0xFF;
	
    while (cbBuf--)
    {
        bFCS = s_ucCrcTbl[bFCS ^ *pBuf++];        
    }

    bFCS = ~bFCS & 0xFF;

    return bFCS;
}


inline DWORD WriteFrame (BYTE* pFrame, BYTE* pData, DWORD cbData)
{
    DWORD cbWritten = 0;

    while (cbData--)
    {
        if (GSM0710_CTRL_OCTET == *pData ||
            GSM0710_ADVANCED_FLAG == *pData)
        {
            *pFrame++ = GSM0710_CTRL_OCTET;
            *pFrame++ = *pData++ ^ 0x20;
            cbWritten += 2;
        }
        else
        {
            *pFrame++ = *pData++;
             cbWritten++;
        }        
    }

    return cbWritten;
}

BOOL CChanel::AdvancedModeSend (tFrameData* pFrameData)
{
    BOOL Ret = FALSE;

    EnterCriticalSection(&m_csSendLocker);

    if (m_pSend != NULL)
    {
        //Note:
        //   1) FCS is calculated only on address and control (no UI frames)
        //   2) Send buffer has enough space to hold flags, header and max 
        //      frame size worth of data, including octets added for transparency
        //      (see CChanel::Init function)
        
        BYTE  bFCS;
        BYTE  Trailer[3];
        DWORD cbTrailer;
        BYTE* pDataStart;
        DWORD cbFraming;        
        DWORD cbDataLeft;
        BYTE* pDataBuf;        

        //Header
        *m_pSend = m_bFrameFlag;
        pDataStart = m_pSend + 1;        
        cbFraming = WriteFrame(pDataStart, pFrameData->Header, sizeof(pFrameData->Header));
        pDataStart += cbFraming++;        

        //Trailer
        bFCS = CalcFCS(pFrameData->Header, sizeof(pFrameData->Header));
        cbTrailer = WriteFrame(Trailer, &bFCS, 1); 
        Trailer[cbTrailer++] = m_bFrameFlag; 

        cbFraming += cbTrailer; 
        
        cbDataLeft = pFrameData->cbData;
        pDataBuf = pFrameData->pData;

        Ret = TRUE;

        do
        {
            DWORD cbFrame;
            BYTE* pFrame = pDataStart;             
            DWORD cbWrite = (cbDataLeft > m_cbMaxFrameData) ? m_cbMaxFrameData : cbDataLeft;

            cbFrame = WriteFrame(pFrame, pDataBuf, cbWrite);
            pFrame += cbFrame;
            
            memcpy(pFrame, Trailer, cbTrailer);
            cbFrame += cbFraming;
            
            if (cbFrame != s_Dispatcher.Send(m_pSend, cbFrame))
            {
             	SENDERR((TEXT("710MUX: Chnl %s: -> send frame failed\r\n"), m_pName)) ;
                Ret = FALSE;
                break;
            }

            pDataBuf += cbWrite;
            cbDataLeft -= cbWrite;
        } 
        while (cbDataLeft > 0);
        
    }
    
    LeaveCriticalSection(&m_csSendLocker);

    return Ret;
}

BOOL CChanel::BasicModeSend (tFrameData* pFrameData)
{
    BOOL Ret = FALSE;

    EnterCriticalSection(&m_csSendLocker);

    if (m_pSend != NULL)
    {
        BYTE* pDataStart;
        DWORD cbFraming;
        DWORD cbDataLeft;
        BYTE* pDataBuf; 

        //Header
        *m_pSend = m_bFrameFlag;
        pDataStart = (m_pSend + 1);        
        memcpy(pDataStart, pFrameData->Header, sizeof(pFrameData->Header));
        pDataStart += sizeof(pFrameData->Header);
        cbFraming = sizeof(pFrameData->Header) + 3; //header + 2 flag octets + FCS octet

        Ret = TRUE;
        
        cbDataLeft = pFrameData->cbData;
        pDataBuf = pFrameData->pData;
        
        do
        {   
            DWORD cbFrame;
            BYTE  bFCS;
            BYTE* pFrame;             
            DWORD cbData;
            
            pFrame = pDataStart;
            cbData = (cbDataLeft > m_cbMaxFrameData) ? m_cbMaxFrameData : cbDataLeft;            
            cbFrame = 0;

//            /* For SABM and DISC, it needn't file the information and length field */
//            if ((CTRL_FIELD_SABM != pFrameData->Header[1])
//                && (CTRL_FIELD_DISC != pFrameData->Header[1]))
//            {
                //Set the length
                if (cbData > 0x7F)
                {
                    //Extended length
                    *pFrame++ = (((BYTE)cbData) << 1);
                    *pFrame++ = (BYTE)(cbData >> 7);
                    cbFrame += 2;
                }
                else
                {
                    *pFrame++ = (((BYTE)cbData) << 1) | SET_EA_MASK;
                    cbFrame++;
                }
//            } 
            
            //FCS is calculated over address, control and 
            //length fields (UI frames are not supported)
            bFCS = CalcFCS((m_pSend + 1), sizeof(pFrameData->Header) + cbFrame);

            //Copy data
            if (pDataBuf)
            {
                memcpy (pFrame, pDataBuf, cbData);
                cbFrame += cbData;
                pFrame += cbData;
            }

            //Add FCS and closing flag
            *pFrame++ = bFCS;
            *pFrame = m_bFrameFlag;

            cbFrame += cbFraming;

            if (cbFrame != s_Dispatcher.Send(m_pSend, cbFrame))
            {
             	SENDERR((TEXT("710MUX: Chnl %s: -> send frame failed\r\n"), m_pName)) ;
                Ret = FALSE;
                break;
            }

            pDataBuf += cbData;
            cbDataLeft -= cbData;
        }
        while (cbDataLeft > 0);
    }
    
    LeaveCriticalSection(&m_csSendLocker);

    return Ret;
}

bool CChanel::SendFrameWaitAck (BYTE bControl)
{
    bool fRet = false ;
    DWORD dwTimeOut = GSM0710_T1 ;
    //DWORD dwTimeOut = GSM0710_T3;
    tFrameData FrameData = {0};


    // GSM 7.10 5.4.4.1: there could be only one outstanding poll frame
    //-------------------------
    EnterCriticalSection(&m_csPollFrameLocker) ;

    // set frame content
    FrameData.Header[0] = m_bAddress;
    FrameData.Header[1] = bControl; 
    FrameData.cbData = 0;
    FrameData.pData = NULL;    
    
    if (!((this->*m_pfnSend)(&FrameData)))
    {
        goto error;
    }

    // wait for the answer
    //---------------------
    if ( WAIT_TIMEOUT == WaitForSingleObject(m_hRespEv, dwTimeOut) || !m_bIsOk )
    {
        goto error;
    }

    if (CTRL_FIELD_UA != m_bControl)
    {
        goto error ;
    }

    fRet = true ;
    
error:
    LeaveCriticalSection(&m_csPollFrameLocker) ;
	
    return fRet ;
}

bool CChanel::SendSABM()
{
    SENDLOG((TEXT("710MUX: Chnl %s: -> send SABM\r\n"), m_pName));

    return SendFrameWaitAck(CTRL_FIELD_SABM);
}

bool CChanel::SendDISC()
{
    SENDLOG((TEXT("710MUX: Chnl %s: -> send DISC\r\n"), m_pName));

    return SendFrameWaitAck(CTRL_FIELD_DISC);
}

DWORD CChanel::SendUIH(BYTE *pInBuf, DWORD dwLen, BYTE bUIHControl)
{
	tFrameData FrameData;

    SENDLOG((TEXT("710MUX: Chnl %s: -> send UIH\r\n"), m_pName)) ;
	
    //set frame content
    FrameData.Header[0] = m_bAddress;
    FrameData.Header[1] = bUIHControl;
    FrameData.pData = pInBuf;
    FrameData.cbData = dwLen;

    if (!((this->*m_pfnSend) (&FrameData)))
    {
        dwLen = (DWORD)-1;
    }

    return dwLen ;
}

void CChanel::SendUA()
{
    tFrameData FrameData = {0};

    SENDLOG((TEXT("710MUX: Chnl %s: -> send UA\r\n"), m_pName)) ;

    //set frame content
    FrameData.Header[0] = m_bAddress & CLEAR_CR_MASK;
    FrameData.Header[1] = CTRL_FIELD_UA;

    (this->*m_pfnSend) (&FrameData);
}

void CChanel::SendDM()
{
    tFrameData FrameData = {0};

    SENDLOG((TEXT("710MUX: Chnl %s: -> send DM\r\n"), m_pName)) ;

    //set frame content
    FrameData.Header[0] = m_bAddress & CLEAR_CR_MASK;
    FrameData.Header[1] = CTRL_FIELD_DM;

    (this->*m_pfnSend) (&FrameData);
}

void CChanel::SignalData()
{
    //Implemented by derived class
    return;
}

void CChanel::SetModemStatus(BYTE v24Sig)
{
    //Implemented by derived class
    return;
}

void CChanel::SignalEndOfFrame(BYTE bControl, BYTE* pData, DWORD cbData) 
{
    if (CTRL_FIELD_UIH == (bControl | SET_PF_MASK))
    {
        if (cbData)
        {
            SENDLOG((TEXT("710MUX: Chnl %s: -> rec UIH\r\n"), m_pName)) ;
            
            // -> get buffer access
            EnterCriticalSection(&m_csReadLocker);

            if (m_pRead == m_pWrite)
            {
                m_pWrite = m_pRead = m_pRec;
            }

            if (cbData <= (DWORD)(m_pRec + m_cbRecBuf - m_pWrite))
            {
                memcpy(m_pWrite, pData, cbData);
                m_pWrite += cbData;
            }
            else
            {
                SENDERR((TEXT("710MUX: Chnl %s: -> Rx buffer is full\r\n"), m_pName));
            }

            // <- release buffer access
            LeaveCriticalSection(&m_csReadLocker);

            m_bUIHControl = bControl;

            SignalData();
        }
    }
    else
    {
        // wake up protocol thread
        m_bControl = bControl;
	    SetEvent(m_hEndOfFrameEv);
    }
}

//====================================================================================================
// User Channel Class
//====================================================================================================
CUserChanel::CUserChanel(BYTE bDlci, bool bIsATChnl, DWORD dwActivityTimeOut, const wchar_t *pName)
: CChanel(bDlci, UserChannelTh, UserChnlTerminate) 
{
    // THe initialization flag of user channel
    m_bInitFlag = FALSE;
    
    // clear internal structures
    memset(&m_dcb, 0, sizeof(DCB)) ;

    if (dwActivityTimeOut)
    {
        m_bMonitorActivity = true ;
        m_dwActivityTimeOut = dwActivityTimeOut ;
    }
    else
    {
        m_bMonitorActivity = false ;
        m_dwActivityTimeOut = 0 ;
    }

    m_hMonitorActivityTh = NULL ;
    m_hChanelBusyEv = NULL ;
    m_lLastActivityReport = ACTIVITY_NO_REPORT ;

    // set default time out to do no time-out
    memset(&m_CommTimeouts, 0, sizeof(COMMTIMEOUTS)) ;
    
    //init
    m_hCommEvent = NULL;

    m_bATChnl = bIsATChnl;

    // save channel name
    if (NULL != pName)
    {
        int iLen = (wcslen(pName) + 1) * 2 ;
        this->m_pName = (wchar_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, iLen) ;
        if ( NULL != this->m_pName )
        {
            memcpy(this->m_pName, pName, iLen) ;
        }
    }
    else
    {
        this->m_pName = g_szNullName ;
    }
    
    // dynamical part of the init
    //
    // rem : Init() will call Terminate() 
    //		 in case of failure
    //-------------------------------------
    if (!Init())
    {
        SENDERR((TEXT("710MUX: User Channel Init Failed!\n"))) ;        
        return ;
    }

    m_bInitFlag = TRUE;
    return;
}

CUserChanel::~CUserChanel()
{
    if(NULL != m_hCommEvent)
    {
        // wake up any thread that wait for comm event
        m_fAbort = true ;
        SetEvent(m_hCommEvent) ;

        CloseHandle(m_hCommEvent) ;
        m_hCommEvent = NULL ;
    }
   
    if ( NULL != m_pName && m_pName != g_szNullName)
    {
        HeapFree(GetProcessHeap(), 0, m_pName) ;
        m_pName = NULL ;
    }
}

// Init and Terminate 
//
// rem :
//	- these methods are called int the root class
//	  Init or Terminate
//	- Terminate is implemented by means 
//	  of friend routine UserChnlTerminate
//-------------------------------------------------
bool CUserChanel::Init() 
{
    // User channel specific init
    if (NULL == m_hCommEvent)
    {
        // only create this m_hCommEvent once 
        m_hCommEvent = CreateEvent(0, FALSE, FALSE, NULL) ;
        if (NULL == m_hCommEvent)
        {
            goto error ;
        }
    }

    m_fEventMask = 0;
    m_fEventData = 0;

    m_ModemStatus = MS_CTS_ON | MS_DSR_ON | MS_RLSD_ON;

    // standard channel init
    if (!CChanel::Init())
    {
        m_ModemStatus = 0;
        goto error ;
    }

    //Send modem status
    SendModemStatus(0);

    // negotiate service if not reserved to AT cmd 
    // 
    // Rem: AT cmd is default case (GSM 07.10 ?5.4.6.3.11)
    if ( !m_bATChnl )
    {
        if (!s_Dispatcher.NegotiateService(GetDLCI()))
        {
            goto error;
        }
    }

    // activity report
    //
    // rem : this must be done after full channel init
    if( m_bMonitorActivity )
    {
        m_lTimeSinceLastActivityNotif = GetTickCount() ;

        if ( NULL == m_hChanelIdleEv && NULL == m_hChanelBusyEv	&& NULL == m_hMonitorActivityTh )
        {
            m_hChanelBusyEv = CreateEvent(0,FALSE,FALSE,NULL) ;
            m_hChanelIdleEv = CreateEvent(0,FALSE,FALSE,NULL) ;

            if ( NULL != m_hChanelBusyEv && NULL != m_hChanelIdleEv )
            {
                m_hMonitorActivityTh = CreateThread(NULL,0, MonitorActivityTh, (LPVOID)this, 0,NULL) ;
            }
            else
            {
                if (NULL != m_hChanelBusyEv)
                {
                    CloseHandle(m_hChanelBusyEv) ;
                    m_hChanelBusyEv = NULL ;
                }

                if (NULL != m_hChanelIdleEv)
                {
                    CloseHandle(m_hChanelIdleEv) ;
                    m_hChanelIdleEv = NULL ;
                }
            }

            if ( NULL == m_hMonitorActivityTh )
            {
                // no activity monitoring
                m_bMonitorActivity = false ;
                m_dwActivityTimeOut = 0 ;
                m_lTimeSinceLastActivityNotif = 0 ;
            }
        }
    }

    return true ;

error:	
    if (m_bIsOk)
    {
        // channel should be terminated
        Terminate() ;
    }

    return false ;
}

//----------------------------------------------------
// return number of bytes available in the Rx buffer
//----------------------------------------------------
DWORD CUserChanel::GetRxBytesAvailable()
{
    DWORD dwBytesNb = 0 ;

    // -> get buffer access
    EnterCriticalSection(&m_csReadLocker);

    dwBytesNb = m_pWrite - m_pRead;

    // <- release buffer access
    LeaveCriticalSection(&m_csReadLocker);

    return dwBytesNb ;
}

DWORD CUserChanel::ReadData (BYTE *pInBuf, DWORD dwLen)
{
    // -> get buffer access
    EnterCriticalSection(&m_csReadLocker);

    if (m_pRead != NULL)
    {
        DWORD cbAvail = m_pWrite - m_pRead;
        dwLen = (dwLen > cbAvail) ? cbAvail : dwLen;

        memcpy (pInBuf, m_pRead, dwLen);
        m_pRead += dwLen;
    }
    else
    {
        SetLastError(ERROR_NOT_READY);
        dwLen = (DWORD)-1;
    }

    // <- release buffer access
    LeaveCriticalSection(&m_csReadLocker);

    return dwLen;
}

DWORD CUserChanel::GetModemStatus()
{
    return m_ModemStatus;
}

void CUserChanel::SetModemStatus(BYTE v24Sig)
{
    SENDLOG((TEXT("710MUX: Chnl %s: -> modem status 0x%x\r\n"), m_pName, (v24Sig & ~SET_EA_MASK))) ;

    DWORD dwModemStatus = 0;

    if ((v24Sig & CTRL_MSC_CTS_BIT) && !(v24Sig & CTRL_MSC_FC_BIT))
    {
        dwModemStatus |= MS_CTS_ON;
    }
    if (v24Sig & CTRL_MSC_DSR_BIT)
    {
        dwModemStatus |= MS_DSR_ON;
    }
    if (v24Sig & CTRL_MSC_RI_BIT)
    {
        dwModemStatus |= MS_RING_ON;
    }
    if (v24Sig & CTRL_MSC_DCD_BIT)
    {
        dwModemStatus |= MS_RLSD_ON;
    }

    m_ModemStatus = dwModemStatus;
}

bool CUserChanel::SendModemStatus(DWORD dwCode)
{
    BYTE v24Sig = CTRL_MSC_MS_STATUS;

    if (IOCTL_SERIAL_CLR_DTR == dwCode)
    {
        v24Sig &= ~CTRL_MSC_DSR_BIT;
    }

    if (IOCTL_SERIAL_CLR_RTS == dwCode )
    {
        v24Sig &= ~CTRL_MSC_CTS_BIT;
    }

    if (IOCTL_SERIAL_SET_XOFF == dwCode)
    {
        v24Sig |= CTRL_MSC_FC_BIT;
    }

    SENDLOG((TEXT("710MUX: Chnl %s: -> sending modem status 0x%x\r\n"), m_pName, v24Sig)) ;

    return s_Dispatcher.SendModemStatus(GetDLCI(), v24Sig);
}

//------------------------------------------------
// notify idle state
//------------------------------------------------
void CUserChanel::NotifyIdle()
{
    if(m_bMonitorActivity && CHANNEL_IDLE != m_lLastActivityReport && NULL != m_hChanelIdleEv)
    {
        SetEvent(m_hChanelIdleEv) ;
    }
}

//------------------------------------------------
// notify busy state
//------------------------------------------------
void CUserChanel::NotifyBusy()
{
    if (m_bMonitorActivity )
    {
        // update time since last activity notif
        InterlockedExchange(&m_lTimeSinceLastActivityNotif, GetTickCount()) ;

        // signal activity
        if ( CHANNEL_BUSY != m_lLastActivityReport && NULL != m_hChanelBusyEv )
        {
            SetEvent(m_hChanelBusyEv) ;
        }
    }

}

void CUserChanel::SignalData()
{
    // Activity monitoring
    NotifyBusy();

    // indicate that there are bytes to be read
    InterlockedExchange((LONG*)&m_fEventData, EV_RXCHAR );
    PulseEvent(m_hCommEvent);
}

// friend routine
//-----------------

bool UserChnlTerminate(PVOID lpParameter)
{
    CUserChanel* pChanel = (CUserChanel*)lpParameter;

    // activity report
    if( NULL != pChanel->m_hMonitorActivityTh )
    {
        // terminate monitoring thread

        SetEvent(pChanel->m_hChanelBusyEv) ;
        WaitForSingleObject(pChanel->m_hMonitorActivityTh, INFINITE) ;

        CloseHandle(pChanel->m_hMonitorActivityTh) ;
        pChanel->m_hMonitorActivityTh = NULL ;

        CloseHandle(pChanel->m_hChanelBusyEv) ;
        pChanel->m_hChanelBusyEv = NULL ;
        CloseHandle(pChanel->m_hChanelIdleEv) ;
        pChanel->m_hChanelIdleEv = NULL ;
    }

    return true ;
}

// GSM 07.10
//-----------

DWORD WINAPI UserChannelTh( PVOID lpParameter )
{
    CUserChanel* pChanel = (CUserChanel*)lpParameter;
    DWORD dwWaitStatus ;

    __try 
    {
        // Set priority
        SetThreadPriority(pChanel->m_hProtocolTh, THREAD_PRIORITY_ABOVE_NORMAL) ;

        while(!pChanel->m_bTerminate)
        {
            dwWaitStatus = WaitForSingleObject(pChanel->m_hEndOfFrameEv, INFINITE) ;
            
            if (pChanel->m_bTerminate)
            {
                // termination requested
                break;
            }

            // Activity monitoring
            pChanel->NotifyBusy();

            switch(pChanel->m_bControl | SET_PF_MASK)
            {
                case CTRL_FIELD_SABM:
                {
                    SENDLOG((TEXT("710MUX: Chnl %s: -> rec SABM\r\n"), pChanel->m_pName)) ;
                    // set connected state
                    InterlockedExchange(&(pChanel->m_lState), CONNECTED) ;

                    // acknowledge: send UA
                    pChanel->SendUA() ;

                    // reset event (in case of no one has been waiting for it since disconnect time)
                    ResetEvent(pChanel->m_hCommEvent);
                }
                break ;

                case CTRL_FIELD_UA:
                case CTRL_FIELD_DM:
                {
                    SENDLOG((TEXT("710MUX: Chnl %s: -> rec UA/DM (0x%x)\r\n"), pChanel->m_pName, (pChanel->m_bControl | SET_PF_MASK))) ;
                    SetEvent(pChanel->m_hRespEv) ;
                }
                break ;

                case CTRL_FIELD_DISC:
                {
                    SENDLOG((TEXT("710MUX: Chnl %s: -> rec DISC\r\n"), pChanel->m_pName)) ;
                    // set disconnected state
                    InterlockedExchange(&(pChanel->m_lState), DISCONNECTED) ;

                    // acknowledge: send UA
                    pChanel->SendUA();
                }
                break ;
            }            
        }
    }
    __except( ChannelFilterException(GetExceptionCode(), TEXT("UserChannelTh")) ) 
    {
    }

	return 0 ;
}

DWORD WINAPI MonitorActivityTh( PVOID lpParameter )
{
#define EV_NB_MONITOR_TH 2
#define EV_BUSY_MONITOR_TH 0
#define EV_IDLE_MONITOR_TH 1    

    CUserChanel* pChanel = (CUserChanel*)lpParameter;
    
    DWORD dwTimeOut;
    DWORD dwWaitStatus ;
    DWORD dwTimeSinceLastReport;
    DWORD dwCurTime ;
    bool bSignalIdle = false ;

    HANDLE hAwaitedEv[EV_NB_MONITOR_TH] ;

    __try 
    {
        dwTimeOut = pChanel->m_dwActivityTimeOut ;
        hAwaitedEv[EV_BUSY_MONITOR_TH] = pChanel->m_hChanelBusyEv ;
        hAwaitedEv[EV_IDLE_MONITOR_TH] = pChanel->m_hChanelIdleEv ;

        // Set priority
        SetThreadPriority(pChanel->m_hMonitorActivityTh, THREAD_PRIORITY_ABOVE_IDLE) ;

        while(!pChanel->m_bTerminate)
        {
            dwWaitStatus = WaitForMultipleObjects(EV_NB_MONITOR_TH, hAwaitedEv, FALSE, dwTimeOut) ;
            if (pChanel->m_bTerminate)
            {
	            // termination requested
                break ;
            }

            // Activity monitoring
            if(WAIT_TIMEOUT == dwWaitStatus || (WAIT_OBJECT_0 + EV_IDLE_MONITOR_TH) == dwWaitStatus) 
            {
                if (CHANNEL_IDLE != pChanel->m_lLastActivityReport)
                {
                    if(WAIT_TIMEOUT == dwWaitStatus)
                    {
                        // check it is more than time-out since last activity report
                        bSignalIdle = false ;
                        dwTimeSinceLastReport = pChanel->m_lTimeSinceLastActivityNotif ;
                        dwCurTime = GetTickCount() ;
                        if ( dwTimeSinceLastReport > dwCurTime ) 
                        {
                            // dword wrap around  
                            // => update lTimeSinceLastActivityNotif and
                            //    wait next time out
                            InterlockedExchange(&(pChanel->m_lTimeSinceLastActivityNotif), GetTickCount()) ;
                        }
                        else if ((dwCurTime - dwTimeSinceLastReport) >= pChanel->m_dwActivityTimeOut)
                        {
                            //  activity time-out elapsed since last activity report
                            bSignalIdle = true ;
                        }
                    }
                    else
                    {
                        // idle has been directly notified (ctrl channel PSC) 
                        // => no need to check time since last activity
                        bSignalIdle = true ;
                    }

                    if (bSignalIdle)
                    {
                        // channel is inactif => only wait for activity
                        dwTimeOut = INFINITE ;

                        // signal no activity
                        InterlockedExchange(&(pChanel->m_lLastActivityReport), CHANNEL_IDLE) ;
                        pChanel->NotifyActivity(CHANNEL_IDLE) ;
                    }
                }
            }
            else if (CHANNEL_BUSY != pChanel->m_lLastActivityReport)
            {
                // channel is actif => wait with time out
                dwTimeOut = pChanel->m_dwActivityTimeOut ;

                // signal no activity
                InterlockedExchange(&(pChanel->m_lLastActivityReport), CHANNEL_BUSY) ;
                pChanel->NotifyActivity(CHANNEL_BUSY) ;
            }
        }

        // terminating channel => signal idle
        if (CHANNEL_IDLE != pChanel->m_lLastActivityReport)
        {
            InterlockedExchange(&(pChanel->m_lLastActivityReport), CHANNEL_IDLE) ;
            pChanel->NotifyActivity(CHANNEL_IDLE) ;
        }
    }
    __except( ChannelFilterException(GetExceptionCode(), TEXT("MonitorActivityTh")) ) 
    {
    }

    return 0 ;
}

//====================================================================================================
// Control Channel Class
//====================================================================================================

CCtrlChanel::CCtrlChanel(BYTE bDlci)
    : CChanel(bDlci, CtrlChannelTh, CtrlChnlTerminate) 
{
    m_pName = TEXT("Ctrl") ;

    m_hEndOfRspProcessing = NULL ;
    m_hDataEv = NULL;

    // create critical section
    InitializeCriticalSection(&m_csCommandLocker) ;
    InitializeCriticalSection(&m_csPSCNegotiation) ;

    // dynamical part of the init
    //
    // rem : Init() will call Terminate() 
    //		 in case of failure
    //-------------------------------------
    Init();
}

CCtrlChanel::~CCtrlChanel()
{
    // delete critical section
    //--------------------------
    DeleteCriticalSection(&m_csCommandLocker) ;
    DeleteCriticalSection(&m_csPSCNegotiation) ;
}

// Init and Terminate 
//
// rem :
//	- these methods are called int the root class
//	  Init or Terminate
//	- Terminate is implemented by means 
//	  of friend routine UserChnlTerminate
//-------------------------------------------------
bool CCtrlChanel::Init() 
{
    // Control channel specific init
    if (NULL == m_hEndOfRspProcessing)
    {
        m_hEndOfRspProcessing = CreateEvent(0,FALSE,FALSE,NULL) ;
        if (NULL == m_hEndOfRspProcessing)
        {
            goto error ;
        }
    }

    if (NULL == m_hDataEv)
    {
        m_hDataEv = CreateEvent(0,FALSE,FALSE,NULL);
        if (NULL == m_hDataEv)
        {
            goto error;
        }
    }

    m_CurCmd.bType = 0;
    m_CurCmd.cbData = 0;

    // standard channel init
    if (!CChanel::Init())
    {
        goto error ;
    }

	return true ;

error:
	if (m_bIsOk)
    {
        // channel should be terminated
        Terminate() ;
    }

    return false ;
}

// GSM 07.10
//-----------
bool CCtrlChanel::NegotiateService(BYTE bDlci)
{
    bool fRet = false ;
    DWORD dwTimeOut = GSM0710_T2 ;
    BYTE UIHData[5];    

    SENDLOG((TEXT("710MUX: DLCI %u: -> Negotiate service\r\n"), bDlci)) ;

    // -> get command access
    EnterCriticalSection(&m_csCommandLocker);

    UIHData[0] = CTRL_UIH_SNC | SET_CR_MASK | SET_EA_MASK ;
    UIHData[1] = (CTRL_SNC_MSG_LENGTH << 1) | SET_EA_MASK ;
    UIHData[2] = (bDlci << 2) | SET_CR_MASK | SET_EA_MASK  ;
    UIHData[3] = CTRL_UIH_SNC_DATA | SET_EA_MASK ;
    UIHData[4] = SET_EA_MASK ;

    // reset end of processing answer event
    //
    // rem : due to time out mgt this event could have
    //		 been set but not used by the protocol thread
    //		 simply because it has received no answer
    ResetEvent(m_hEndOfRspProcessing) ;

    // send the frame
    //----------------
    if ((DWORD)-1 == SendUIH(UIHData, sizeof(UIHData), CTRL_FIELD_UIH))
    {
        goto error;
    }

    // wait for the answer
    //---------------------
    if ( WAIT_TIMEOUT == WaitForSingleObject(m_hRespEv, dwTimeOut) || !m_bIsOk )
    {
        SENDERR((TEXT("710MUX: Chnl %s: -> timeout waiting for SN rsp\r\n"), m_pName)) ;
        goto error ;
    }

    // check the received UIH    
    if ( ((CTRL_UIH_SNC | SET_EA_MASK) & CLEAR_CR_MASK) == m_CurCmd.bType  &&
          CTRL_SNC_MSG_LENGTH  == m_CurCmd.cbData &&
          bDlci  == (m_CurCmd.Data[0] >> 2) &&
         (CTRL_UIH_SNC_DATA | SET_EA_MASK) == m_CurCmd.Data[1] )
    {
        SENDLOG((TEXT("710MUX: Chnl %s: -> got SN rsp\r\n"), m_pName)) ;
        fRet = true ;
    }
    else
    {
        SENDERR((TEXT("710MUX: Chnl %s: -> invalid SN rsp\r\n"), m_pName)) ;
    }
    
error:
    // signal end of processing answer
    //
    // Rem:
    //
    //	- this event must be set even in error condition
    //	  otherwise the protocol thread will blocked for ever
    //	  (see ParseUIH method)
    //
    //	- this event MUST be set before releasing command access
    //	  (because it signals to the protocol thread that the
    //	   answer to a request has been taken into account)
    SetEvent(m_hEndOfRspProcessing) ;

    // <- release command access
    LeaveCriticalSection(&m_csCommandLocker);

    return fRet ;
}

bool CCtrlChanel::SendModemStatus(BYTE bDlci, BYTE v24Sig)
{
    bool fRet = false ;
    DWORD dwTimeOut = GSM0710_T2 ;
    BYTE bUIHData[4];    

    SENDLOG((TEXT("710MUX: DLCI %u: -> Send modem status 0x%x\r\n"), bDlci, v24Sig)) ;

    // -> get command access
    EnterCriticalSection(&m_csCommandLocker);

    bUIHData[0] = CTRL_UIH_MSC | SET_CR_MASK | SET_EA_MASK ;
    bUIHData[1] = (CTRL_MSC_MSG_LENGTH << 1) | SET_EA_MASK ;
    bUIHData[2] = (bDlci << 2) | SET_CR_MASK | SET_EA_MASK  ;
    bUIHData[3] = v24Sig | SET_EA_MASK ;

    // reset end of processing answer event
    //
    // rem : due to time out mgt this event could have
    //		 been set but not used by the protocol thread
    //		 simply because it has received no answer
    ResetEvent(m_hEndOfRspProcessing) ;

    // send the frame
    //----------------
    if ((DWORD)-1 == SendUIH(bUIHData, sizeof(bUIHData), CTRL_FIELD_UIH))
    {
        goto error;
    }

    // wait for the answer
    //---------------------
    if ( WAIT_TIMEOUT == WaitForSingleObject(m_hRespEv, dwTimeOut) || !m_bIsOk )
    {
        SENDERR((TEXT("710MUX: Chnl %s: -> timeout waiting for modem status rsp\r\n"), m_pName)) ;
        goto error ;
    }

    // check the received UIH
    if ( ((CTRL_UIH_MSC | SET_EA_MASK) & CLEAR_CR_MASK) == m_CurCmd.bType &&
          CTRL_MSC_MSG_LENGTH == m_CurCmd.cbData &&
         ((bDlci << 2) | SET_CR_MASK | SET_EA_MASK) == m_CurCmd.Data[0] )
    {
        // command is OK
        SENDLOG((TEXT("710MUX: Chnl %s: -> got modem status rsp\r\n"), m_pName)) ;
        fRet = true ;
    }
    else
    {
        SENDERR((TEXT("710MUX: Chnl %s: -> invalid modem status rsp\r\n"), m_pName)) ;
    }
   
error:
    // signal end of processing answer
    //
    // Rem:
    //
    //	- this event must be set even in error condition
    //	  otherwise the protocol thread will blocked for ever
    //	  (see ParseUIH method)
    //
    //	- this event MUST be set before releasing command access
    //	  (because it signals to the protocol thread that the
    //	   answer to a request has been taken into account)
    SetEvent(m_hEndOfRspProcessing) ;

    // <- release command access
    LeaveCriticalSection(&m_csCommandLocker);

    return fRet ;
}

bool CCtrlChanel::EnterPowerSavingMode()
{
    bool fRet = true ;
    DWORD dwTimeOut = GSM0710_T2 ;

    if (TryEnterCriticalSection(&m_csPSCNegotiation))
    {
        SENDLOG((TEXT("710MUX: Chnl %s: -> Send PSC\r\n"), m_pName)) ;
        
        BYTE UIHData[2];

        // -> get command access
        EnterCriticalSection(&m_csCommandLocker);

        // type field	= PSC
        // lenght		= 0
        UIHData[0] = CTRL_UIH_PSC | SET_CR_MASK | SET_EA_MASK ;
        UIHData[1] = SET_EA_MASK ;				

        // reset end of processing answer event
        //
        // rem : due to time out mgt this event could have
        //		 been set but not used by the protocol thread
        //		 simply because it has received no answer
        ResetEvent(m_hEndOfRspProcessing) ;

        // send the frame
        //----------------
        SendUIH(UIHData, sizeof(UIHData), CTRL_FIELD_UIH) ;

        // wait for the answer
        //
        // No need to check the received UIH
        // since the Mux driver will have to 
        // proceed to a wake up process in any cases
        //
        // Rem : 
        //	- this is valid since command are processed
        //	  one after each other
        //----------------------------------------------------
        WaitForSingleObject(m_hRespEv, dwTimeOut)  ;


        // tell dispatcher that TE is in Power Saving mode
        //
        // Rem : this must be done after sending answer to TE
        s_Dispatcher.SetPowerSavingMode() ;

        // signal end of processing answer
        //
        // Rem:
        //
        //	- this event must be set even in error condition
        //	  otherwise the protocol thread will blocked for ever
        //	  (see ParseUIH method)
        //
        //	- this event MUST be set before releasing command access
        //	  (because it signals to the protocol thread that the
        //	   answer to a request has been taken into account)
        SetEvent(m_hEndOfRspProcessing) ;

        // <- release command access
        LeaveCriticalSection(&m_csCommandLocker);

        // <- Power saving procedure is now finished
        LeaveCriticalSection(&m_csPSCNegotiation);
    }

    return fRet ;
}

bool CCtrlChanel::GetNextCtrlMsg(tCtrlMsg* pMsg)
{    
    bool bMsg = false;    
    
    enum
    {
        MSG_TYPE,
        MSG_LENGTH_1,
        MSG_LENGTH_2,
        MSG_DATA
    } 
    ParseMsgState;

    // -> get buffer access
    EnterCriticalSection(&m_csReadLocker);

    ParseMsgState = MSG_TYPE;
    
    while (!bMsg && m_pRead < m_pWrite)
    {
        switch (ParseMsgState)
        {
            case MSG_TYPE:
            {
                pMsg->bType = *m_pRead++;
                ParseMsgState = MSG_LENGTH_1;
            }
            break;

            case MSG_LENGTH_1:
            {
                ParseMsgState = MSG_DATA;
                pMsg->cbData = ((DWORD)*m_pRead) >> 1;
                
                BYTE bType = (pMsg->bType & CLEAR_CR_MASK & CLEAR_EA_MASK);

                // GSM 07.10 stipulate :
                //	- lenght byte contains value 0 (=> 1 lenght byte alltough EA = 0) for :
                //      - CLD,      ?5.4.6.3.2  
                //      - PSC,      ?5.4.6.3.3 
                //      - FCON,     ?5.4.6.3.5 
                //      - FCOFF,	?5.4.6.3.6
		        if ((SET_EA_MASK & *m_pRead) ||
			        CTRL_UIH_CLD == bType    ||
			        CTRL_UIH_PSC == bType    ||
			        CTRL_UIH_FCON == bType   ||
			        CTRL_UIH_FCOFF == bType )
                {
                    //Length is 1 byte long
                    if (0 == pMsg->cbData)
                    {                        
                        bMsg = true;
                    }
                }
                else
                {
                    ParseMsgState = MSG_LENGTH_2;
                }

                m_pRead++;
            }
            break;

            case MSG_LENGTH_2:
            {
                ParseMsgState = MSG_DATA;
                pMsg->cbData |= ((DWORD)*m_pRead) << 7 ;
                if (0 == pMsg->cbData)
                {
                    bMsg = true;
                }
                m_pRead++;
            }
            break;

            case MSG_DATA:
            {
                if (m_pRead + pMsg->cbData <= m_pWrite)
                {
                    //Skip if larger than our buffer
                    if (pMsg->cbData <= sizeof (pMsg->Data))
                    {
                        memcpy(pMsg->Data, m_pRead, pMsg->cbData);
                        bMsg = true;
                    }
                    m_pRead += pMsg->cbData;
                }
                else
                {
                    //Ignore remaining data in buffer
                    m_pRead = m_pWrite;
                }
            }
            break;
        }
    }

    // <- release buffer access
    LeaveCriticalSection(&m_csReadLocker);

    return bMsg;
}

bool CCtrlChanel::ParseUIH()
{
    bool bRetVal = true;
    tCtrlMsg CurCmd;
    
    while (!m_bTerminate && GetNextCtrlMsg(&CurCmd))
    {
        if ((CurCmd.bType & SET_CR_MASK))
        {
            // command
            //----------

            BYTE bUIHData[4];

            switch (CurCmd.bType & CLEAR_CR_MASK & CLEAR_EA_MASK)
            {
                case CTRL_UIH_PSC:
                {
                    // Power Saving
                    //--------------

                    // check if PSC procedure already in progress
                    if (TryEnterCriticalSection(&m_csPSCNegotiation))
                    {
                        SENDLOG((TEXT("710MUX: Chnl %s: -> TE PSC\r\n"), m_pName)) ;

                        // check if already in PSC negotiation process

                        // Rem: 
                        //  indicating power saving to dispatcher
                        //  and sending answer to TE shall not
                        //  be interrupted by any other
                        //  sending operation

                        // -> lock send
                        s_Dispatcher.LockSend() ;

                        // type field	= PSC
                        // length		= 0
                        bUIHData[0] = CTRL_UIH_PSC | SET_EA_MASK ;
                        bUIHData[1] = SET_EA_MASK ;				
                        SendUIH(bUIHData, 2, m_bUIHControl) ;

                        // tell dispatcher that TE is in Power Saving mode
                        //
                        // Rem : this must be done after sending answer to TE
                        s_Dispatcher.SetPowerSavingMode() ;

                        // <- unlock send
                        s_Dispatcher.UnlockSend() ;

                        // <- Power saving procedure is now finished
                        LeaveCriticalSection(&m_csPSCNegotiation) ;

                        // broadcast inactivity status
                        s_Dispatcher.BroadcastChnlIdleStatus() ;
                    }
                    else
                    {
                        SENDLOG((TEXT("710MUX: Chnl %s: -> TE PSC ignored (MS initiated PSC)\r\n"), m_pName)) ;
                    }

                }
                break ;

                case CTRL_UIH_CLD:
                {
                    // multiplexer close down
                    //------------------------

                    SENDLOG((TEXT("710MUX: Chnl %s: -> TE CLD\r\n"), m_pName)) ;

                    // acknowledge: 
                    //  - send CLD response (CLD with CR bit cleared)
                    bUIHData[0] = CTRL_UIH_CLD | SET_EA_MASK ;
                    bUIHData[1] = SET_EA_MASK ;				
                    SendUIH(bUIHData, 2, m_bUIHControl) ;

                    // => quit GSM 07.10
                    bRetVal = false ;
                }
                break ;

                case CTRL_UIH_FCON:
                {
                    // Flow control 
                    //	- send operation allowed
                    //----------------------------

                    // Cancel reset request
                    s_Dispatcher.CancelResetRequest() ;

                    // unblock COM_Write operation
                    SetEvent(s_hFCONEv) ;

                    SENDLOG((TEXT("710MUX: Chnl %s: -> FCON\r\n"), m_pName)) ;
                }
                break ;

                case CTRL_UIH_FCOFF:
                {
                    // Flow control
                    //	- send operation forbiden
                    //-----------------------------

                    // block COM_Write operation
                    ResetEvent(s_hFCONEv) ;

                    // request reboot in time-out
                    s_Dispatcher.RequestResetIn(TIME_OUT_BEFORE_RESET) ;

                    SENDLOG((TEXT("710MUX: Chnl %s: -> FCOFF - will reset radio in %ums if no FCON\r\n"), m_pName, TIME_OUT_BEFORE_RESET)) ;
                }
                break ;

                case CTRL_UIH_MSC:
                {
                    //Modem status
                    if (CurCmd.cbData >= CTRL_MSC_MSG_LENGTH)
                    {
                        s_Dispatcher.SetModemStatus((CurCmd.Data[0] >> 2), CurCmd.Data[1]);

                        //Reply with a copy of V.24 signals (GSM 07.10 section 5.4.6.3.7)
                        bUIHData[0] = CTRL_UIH_MSC | SET_EA_MASK ;
                        bUIHData[1] = (CTRL_MSC_MSG_LENGTH << 1) | SET_EA_MASK;
                        bUIHData[2] = CurCmd.Data[0];
                        bUIHData[3] = CurCmd.Data[1];
                        SendUIH(bUIHData, 4, m_bUIHControl);
                    }
                }
                break;

                case CTRL_UIH_SNC:
                case CTRL_UIH_TEST:
                case CTRL_UIH_PN:
                case CTRL_UIH_NSC:
                case CTRL_UIH_RPN:
                case CTRL_UIH_RLS:
                {
                    // send unsupported command 
                    //--------------------------
                    SENDERR((TEXT("710MUX: Chnl %s: -> unsupported request 0x%x\r\n"), m_pName, (CurCmd.bType & CLEAR_CR_MASK & CLEAR_EA_MASK))) ;

                    // type field	= NSC
                    // lenght		= 1
                    // value		= unsupported command code
                    bUIHData[0] = CTRL_UIH_NSC | SET_EA_MASK ;
                    bUIHData[1] = (0x01 << 1) | SET_EA_MASK ;
                    bUIHData[2] = CurCmd.bType ;
                    SendUIH(bUIHData, 3, m_bUIHControl) ;
                }
                break ;
            }
        }
        else
        {
            // response 
            //----------

            // check if Ctrl channel is waiting for an answer
            if (!TryEnterCriticalSection(&m_csCommandLocker))
            {
                // ctrl channel is waiting for an answer to its request
                switch(CurCmd.bType & CLEAR_CR_MASK & CLEAR_EA_MASK)
                {
                    case CTRL_UIH_PSC:
                    case CTRL_UIH_SNC:
                    case CTRL_UIH_MSC:
                    {
                        SENDLOG((TEXT("710MUX: Chnl %s: -> TE answer 0x%x\r\n"), m_pName, (CurCmd.bType & CLEAR_CR_MASK & CLEAR_EA_MASK))) ;

                        m_CurCmd.bType = CurCmd.bType;
                        m_CurCmd.cbData = CurCmd.cbData;
                        if (CurCmd.cbData > 0)
                        {
                            memcpy (m_CurCmd.Data, CurCmd.Data, CurCmd.cbData);
                        }

                        // signal answer has been received
                        SetEvent(m_hRespEv) ;

                        // wait the response to be proceeded
                        //
                        // rem : 
                        //	- this event MUST be set by the request method
                        //	  even in error condition (time-out)
                        WaitForSingleObject(m_hEndOfRspProcessing, INFINITE) ;
                    }
                    break ;

                    case CTRL_UIH_CLD:
					{
	                    SENDERR((TEXT("710MUX: Chnl %s: -> TE answer Mux Closing Down (0x%x)\r\n"), m_pName, (CurCmd.bType & CLEAR_CR_MASK & CLEAR_EA_MASK))) ;
						break;
                   	}
						
                    case CTRL_UIH_TEST:
                    case CTRL_UIH_PN:
                    case CTRL_UIH_FCON:
                    case CTRL_UIH_FCOFF:
                    case CTRL_UIH_NSC:
                    case CTRL_UIH_RPN:
                    case CTRL_UIH_RLS:
                    {
                        // not implemented 
                        // and should not be received since ctrl channel is not sending any of those command
                        SENDERR((TEXT("710MUX: Chnl %s: -> ERR TE answer to not implemented MS request 0x%x\r\n"), m_pName, (CurCmd.bType & CLEAR_CR_MASK & CLEAR_EA_MASK))) ;
                    }
                    break ;
                }
            }
            else
            {
                // unsolicited response => ignore
                SENDERR((TEXT("710MUX: Chnl %s: -> Unwaited TE answer 0x%x\r\n"), m_pName, (CurCmd.bType & CLEAR_CR_MASK & CLEAR_EA_MASK))) ;
                LeaveCriticalSection(&m_csCommandLocker);			
            }
        }
    }

    return bRetVal;
}

void CCtrlChanel::SignalData()
{
    //Signal event for control channel thread
    SetEvent(m_hDataEv);
}

// friend routine
//-----------------
DWORD WINAPI CtrlChannelTh( PVOID lpParameter )
{
    CCtrlChanel* pChanel = (CCtrlChanel*)lpParameter;
    DWORD dwTimeOut = INFINITE ;
    bool bDoTerminate = false ;
    DWORD dwWaitResult;

    __try 
    {
        // Set priority
        SetThreadPriority(pChanel->m_hProtocolTh, THREAD_PRIORITY_HIGHEST) ;

        HANDLE ChnlEvents[2] = {pChanel->m_hEndOfFrameEv, pChanel->m_hDataEv};

        while(!pChanel->m_bTerminate)
        {
            dwWaitResult = WaitForMultipleObjects(2, ChnlEvents, FALSE, dwTimeOut) ;

            if (pChanel->m_bTerminate)
            {
                // termination request => leave
                break ;
            }

            if (WAIT_OBJECT_0 == dwWaitResult)
            {            
                switch(pChanel->m_bControl | SET_PF_MASK)
                {
                    case CTRL_FIELD_SABM :
                    {
                        SENDLOG((TEXT("710MUX: Chnl %s: -> rec SABM\r\n"), pChanel->m_pName)) ;

                        // acknowledge: send UA                        
                        pChanel->SendUA() ;
                    }
                    break ;

                    case CTRL_FIELD_UA :
                    case CTRL_FIELD_DM :
                    {
                        SENDLOG((TEXT("710MUX: Chnl %s: -> rec UA/DM (0x%x)\r\n"), pChanel->m_pName, (pChanel->m_bControl | SET_PF_MASK))) ;
                        SetEvent(pChanel->m_hRespEv) ;
                    }
                    break ;

                    case CTRL_FIELD_DISC :
                    {
                        SENDLOG((TEXT("710MUX: Chnl %s: -> rec DISC\r\n"), pChanel->m_pName)) ;

                        // acknowledge: send UA
                        pChanel->SendUA() ;

                        // => quit GSM 07.10
                        bDoTerminate = true ;
                        pChanel->m_bTerminate = true ;
                    }
                    break ;
                }
            }
            else
            {
                //Got some data in a UIH frame
                if (!pChanel->ParseUIH())
                {
	                // quit GSM 07.10 (=> CLD has been received)
	                bDoTerminate = true ;
	                pChanel->m_bTerminate = true ;
                }
            }
        }

        if (bDoTerminate)
        {
            // DISC or CLD frame have been received => quit GSM 07.10

            // set disconnected state
            InterlockedExchange(&(pChanel->m_lState), DISCONNECTED) ;

            // Rem : Terminate will delete the control channel instance (pChanel here)
            pChanel->s_Dispatcher.Terminate() ;
        }

    } 
    __except( ChannelFilterException(GetExceptionCode(), TEXT("CtrlChannelTh")) ) 
    {
    }

    return 0 ;
}

bool CtrlChnlTerminate(PVOID lpParameter)
{
    CCtrlChanel* pChanel = (CCtrlChanel*)lpParameter;

    if(NULL != pChanel->m_hEndOfRspProcessing)
    {
        // signal to protocol thread end of rsp processing
        SetEvent(pChanel->m_hEndOfRspProcessing) ;

        CloseHandle(pChanel->m_hEndOfRspProcessing) ;
        pChanel->m_hEndOfRspProcessing = NULL ;
    }

    if (NULL != pChanel->m_hDataEv)
    {
        CloseHandle(pChanel->m_hDataEv) ;
        pChanel->m_hDataEv = NULL;
    }

    return true ;
}

